"""
NS-SEM Solver Builder Implementation

Handles building the NS-SEM solver with PETSc-Kokkos integration.
"""

import os
import subprocess
from pathlib import Path
from typing import Dict, List, Any, Optional
import logging

from gcr_solver_manager.extensions.base_builder import BaseBuilder, BuildResult

logger = logging.getLogger(__name__)


class NSsemBuilder(BaseBuilder):
    """
    Builder for NS-SEM (Navier-Stokes Spectral Element Method) solver.
    
    Handles CMake configuration, compilation, and dependency management
    for the NS-SEM solver with PETSc-Kokkos integration.
    """
    
    def __init__(self, extension_name: str):
        super().__init__(extension_name)
        self.build_types = ["Debug", "Release", "RelWithDebInfo"]
        
    def can_build(self, source_path: str) -> bool:
        """
        Check if this builder can build the project at the given path.
        
        Args:
            source_path: Path to source directory
            
        Returns:
            True if builder can handle this project
        """
        source_path_obj = Path(source_path)
        
        # Check for NS-SEM specific files
        ns_sem_indicators = [
            "CMakeLists.txt",  # Should have CMake
            "src/tensor_mat19.cpp",  # Tensor matrix implementation
            "src/multi_cavity_matrix_builder.cpp",  # Multi-cavity support
            "include/ns_sem_petsc.h",  # PETSc integration header
            "src/ns_sem_to_petsc.cpp",  # PETSc conversion
        ]
        
        found_indicators = 0
        for indicator in ns_sem_indicators:
            if (source_path_obj / indicator).exists():
                found_indicators += 1
                logger.debug(f"Found NS-SEM indicator: {indicator}")
        
        # Need at least 3 indicators to be confident
        can_build = found_indicators >= 3
        logger.debug(f"NS-SEM builder can_build: {can_build} (found {found_indicators}/5 indicators)")
        
        return can_build
    
    def detect_build_system(self, source_path: str) -> str:
        """
        Detect the build system used by the project.
        
        Args:
            source_path: Path to source directory
            
        Returns:
            Build system name
        """
        source_path_obj = Path(source_path)
        
        if (source_path_obj / "CMakeLists.txt").exists():
            return "cmake"
        elif (source_path_obj / "Makefile").exists():
            return "make"
        else:
            return "unknown"
    
    def validate_dependencies(self) -> List[str]:
        """
        Validate that build dependencies are available.
        
        Returns:
            List of missing dependencies
        """
        missing = []
        
        # Check for CMake
        try:
            result = subprocess.run(["cmake", "--version"], capture_output=True, text=True)
            if result.returncode != 0:
                missing.append("cmake")
        except FileNotFoundError:
            missing.append("cmake")
        
        # Check for C++ compiler
        for compiler in ["g++", "clang++"]:
            try:
                result = subprocess.run([compiler, "--version"], capture_output=True, text=True)
                if result.returncode == 0:
                    break
            except FileNotFoundError:
                continue
        else:
            missing.append("c++ compiler (g++ or clang++)")
        
        # Check for MPI
        try:
            result = subprocess.run(["mpirun", "--version"], capture_output=True, text=True)
            if result.returncode != 0:
                missing.append("mpi")
        except FileNotFoundError:
            missing.append("mpi")
        
        # Check for PETSc
        if not os.getenv("PETSC_DIR"):
            missing.append("petsc (PETSC_DIR not set)")
        
        # Check for Kokkos (optional but recommended)
        if not os.getenv("Kokkos_ROOT") and not Path("/usr/local/include/Kokkos_Core.hpp").exists():
            logger.warning("Kokkos not found - GPU acceleration may not be available")
        
        return missing
    
    def configure(self, source_path: str, build_path: str, config: Dict[str, Any]) -> BuildResult:
        """
        Configure the build using CMake.
        
        Args:
            source_path: Path to source directory
            build_path: Path to build directory
            config: Build configuration
            
        Returns:
            Build result with success/failure information
        """
        logger.info(f"Configuring NS-SEM build in {build_path}")
        
        # Create build directory
        build_path_obj = Path(build_path)
        build_path_obj.mkdir(parents=True, exist_ok=True)
        
        # Prepare CMake arguments
        cmake_args = [
            "cmake",
            f"-S{source_path}",
            f"-B{build_path}",
            f"-DCMAKE_BUILD_TYPE={config.get('build_type', 'Release')}",
        ]
        
        # Add PETSc configuration if available
        petsc_dir = os.getenv("PETSC_DIR")
        if petsc_dir:
            cmake_args.append(f"-DPETSC_DIR={petsc_dir}")
        
        # Add Kokkos configuration if available
        kokkos_root = os.getenv("Kokkos_ROOT")
        if kokkos_root:
            cmake_args.append(f"-DKokkos_ROOT={kokkos_root}")
        
        # Add MPI configuration
        cmake_args.extend([
            "-DENABLE_MPI=ON",
            "-DENABLE_PETSC=ON",
        ])
        
        # Add GPU support if requested
        if config.get("enable_gpu", False):
            cmake_args.append("-DENABLE_GPU=ON")
            gpu_backend = config.get("gpu_backend", "cuda")
            cmake_args.append(f"-DGPU_BACKEND={gpu_backend.upper()}")
        
        # Add Kokkos support if requested
        if config.get("enable_kokkos", False):
            cmake_args.append("-DENABLE_KOKKOS=ON")
        
        # Add any additional CMake options
        cmake_options = config.get("cmake_options", [])
        cmake_args.extend(cmake_options)
        
        logger.debug(f"CMake command: {' '.join(cmake_args)}")
        
        try:
            result = subprocess.run(
                cmake_args,
                capture_output=True,
                text=True,
                cwd=build_path,
                timeout=config.get("configure_timeout", 300)  # 5 minutes default
            )
            
            if result.returncode == 0:
                logger.info("NS-SEM CMake configuration successful")
                return BuildResult(
                    success=True,
                    message="Configuration completed successfully",
                    build_artifacts=[],
                    logs={
                        "configure_stdout": result.stdout,
                        "configure_stderr": result.stderr
                    }
                )
            else:
                logger.error(f"CMake configuration failed: {result.stderr}")
                return BuildResult(
                    success=False,
                    message=f"Configuration failed: {result.stderr}",
                    build_artifacts=[],
                    logs={
                        "configure_stdout": result.stdout,
                        "configure_stderr": result.stderr
                    }
                )
                
        except subprocess.TimeoutExpired:
            logger.error("CMake configuration timed out")
            return BuildResult(
                success=False,
                message="Configuration timed out",
                build_artifacts=[],
                logs={"error": "Configuration timed out"}
            )
        except Exception as e:
            logger.error(f"CMake configuration error: {e}")
            return BuildResult(
                success=False,
                message=f"Configuration error: {str(e)}",
                build_artifacts=[],
                logs={"error": str(e)}
            )
    
    def build(self, source_path: str, build_path: str, config: Dict[str, Any]) -> BuildResult:
        """
        Build the NS-SEM solver.
        
        Args:
            source_path: Path to source directory
            build_path: Path to build directory
            config: Build configuration
            
        Returns:
            Build result with success/failure information
        """
        logger.info(f"Building NS-SEM solver in {build_path}")
        
        # Determine parallel jobs
        parallel_jobs = config.get("parallel_jobs", os.cpu_count())
        
        # Build command
        build_args = [
            "cmake",
            "--build", build_path,
            "--config", config.get("build_type", "Release"),
            "--parallel", str(parallel_jobs)
        ]
        
        # Add verbose output if requested
        if config.get("verbose", False):
            build_args.append("--verbose")
        
        logger.debug(f"Build command: {' '.join(build_args)}")
        
        try:
            result = subprocess.run(
                build_args,
                capture_output=True,
                text=True,
                timeout=config.get("build_timeout", 1800)  # 30 minutes default
            )
            
            if result.returncode == 0:
                # Find built artifacts
                build_path_obj = Path(build_path)
                artifacts = []
                
                # Look for common NS-SEM executables
                executable_patterns = [
                    "test_tensor_mat19",
                    "test_multi_cavity_matrix", 
                    "test_petsc_integration",
                    "test_fdm_boundary_comm",
                    "test_multi_cavity_boundary_comm",
                    "libns_sem_core.a",
                    "libns_sem_petsc.a"
                ]
                
                for pattern in executable_patterns:
                    for artifact in build_path_obj.rglob(pattern):
                        artifacts.append(str(artifact))
                        logger.debug(f"Found build artifact: {artifact}")
                
                logger.info(f"NS-SEM build successful, found {len(artifacts)} artifacts")
                return BuildResult(
                    success=True,
                    message=f"Build completed successfully with {len(artifacts)} artifacts",
                    build_artifacts=artifacts,
                    logs={
                        "build_stdout": result.stdout,
                        "build_stderr": result.stderr
                    }
                )
            else:
                logger.error(f"Build failed: {result.stderr}")
                return BuildResult(
                    success=False,
                    message=f"Build failed: {result.stderr}",
                    build_artifacts=[],
                    logs={
                        "build_stdout": result.stdout,
                        "build_stderr": result.stderr
                    }
                )
                
        except subprocess.TimeoutExpired:
            logger.error("Build timed out")
            return BuildResult(
                success=False,
                message="Build timed out",
                build_artifacts=[],
                logs={"error": "Build timed out"}
            )
        except Exception as e:
            logger.error(f"Build error: {e}")
            return BuildResult(
                success=False,
                message=f"Build error: {str(e)}",
                build_artifacts=[],
                logs={"error": str(e)}
            )
    
    def clean(self, build_path: str) -> BuildResult:
        """
        Clean the build directory.
        
        Args:
            build_path: Path to build directory
            
        Returns:
            Build result with success/failure information
        """
        logger.info(f"Cleaning NS-SEM build directory: {build_path}")
        
        try:
            build_path_obj = Path(build_path)
            if build_path_obj.exists():
                # Use CMake clean if available
                cmake_clean = ["cmake", "--build", build_path, "--target", "clean"]
                result = subprocess.run(cmake_clean, capture_output=True, text=True)
                
                if result.returncode == 0:
                    logger.info("Clean completed successfully")
                    return BuildResult(
                        success=True,
                        message="Clean completed successfully",
                        build_artifacts=[],
                        logs={"clean_stdout": result.stdout, "clean_stderr": result.stderr}
                    )
                else:
                    # Fallback to manual directory removal
                    import shutil
                    shutil.rmtree(build_path_obj)
                    build_path_obj.mkdir(parents=True, exist_ok=True)
                    
                    logger.info("Clean completed using directory removal")
                    return BuildResult(
                        success=True,
                        message="Clean completed using directory removal",
                        build_artifacts=[],
                        logs={"clean_method": "directory_removal"}
                    )
            else:
                logger.info("Build directory doesn't exist, nothing to clean")
                return BuildResult(
                    success=True,
                    message="Build directory doesn't exist, nothing to clean",
                    build_artifacts=[],
                    logs={}
                )
                
        except Exception as e:
            logger.error(f"Clean error: {e}")
            return BuildResult(
                success=False,
                message=f"Clean error: {str(e)}",
                build_artifacts=[],
                logs={"error": str(e)}
            )
    
    def get_build_info(self, build_path: str) -> Dict[str, Any]:
        """
        Get information about the current build.
        
        Args:
            build_path: Path to build directory
            
        Returns:
            Dictionary with build information
        """
        build_path_obj = Path(build_path)
        info = {
            "build_exists": build_path_obj.exists(),
            "build_system": "cmake",
            "artifacts": [],
            "cmake_cache": {}
        }
        
        if build_path_obj.exists():
            # Find artifacts
            for pattern in ["test_*", "lib*.a", "lib*.so"]:
                artifacts = list(build_path_obj.rglob(pattern))
                info["artifacts"].extend([str(a) for a in artifacts])
            
            # Read CMake cache if available
            cmake_cache = build_path_obj / "CMakeCache.txt"
            if cmake_cache.exists():
                try:
                    cache_content = cmake_cache.read_text()
                    for line in cache_content.split('\n'):
                        if '=' in line and not line.startswith('#'):
                            key, value = line.split('=', 1)
                            if ':' in key:
                                key = key.split(':', 1)[0]
                            info["cmake_cache"][key] = value
                except Exception as e:
                    logger.debug(f"Error reading CMake cache: {e}")
        
        return info